﻿using System;
using System.Activities;
using System.Activities.Validation;
using System.Activities.XamlIntegration;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xaml;
using System.Xml;
using YasharEl.Infrastructure.Diagnostics.Logging;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.ProcessEngine.DomainModel.WorkflowManagement;
using YasharEl.Infrastructure.ResourcesManagement;

namespace YasharEl.Infrastructure.ProcessEngine.DomainModel.Implementation.WorkflowManagement
{
    public class InDirectoryWorkflowActivityFinder : IWorkflowActivityFinder
    {
        #region Fields

        private readonly string _directoryPath;
        private readonly ILogger<InDirectoryWorkflowActivityFinder> logger;
        private readonly IDependencyResolver resolver;

        #endregion

        #region Constructors

        public InDirectoryWorkflowActivityFinder(string directoryPath,
            ILogger<InDirectoryWorkflowActivityFinder> logger, IDependencyResolver resolver)
        {
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not be null.");
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");

            this.logger = logger;
            this.resolver = resolver;

            if (string.IsNullOrWhiteSpace(directoryPath))
            {
                directoryPath = AppDomain.CurrentDomain.BaseDirectory;
                logger.Debug("Directory path not supplied to workflow activity finder therefore using {0}.", directoryPath);
            }
            else
            {
                // Check path is relative
                if (!Path.IsPathRooted(directoryPath))
                {
                    string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, directoryPath);
                    logger.Debug("Directory path supplied to workflow activity finder is relative therefore using {0}.", binPath);
                    directoryPath = binPath;
                }
            }
            _directoryPath = directoryPath;
            logger.Debug("InDirectoryWorkflowActivityFinder configured to search workflow xamls in {0}.", _directoryPath);
        }

        #endregion

        #region IWorkflowActivityFinder Implementation

        public Activity FindWorkflowProgram(string workflowXaml, out IDictionary<string, DynamicActivityProperty> inputs)
        {
            Activity program = null;
            inputs = new Dictionary<string, DynamicActivityProperty>();
            ActivityXamlServicesSettings xamlSettings = new ActivityXamlServicesSettings
            {
                CompileExpressions = true
            };
            
            FileStream fileStream = null;
            string workflowPath = Path.Combine(_directoryPath, workflowXaml);
            
            #region Find And Load Program

            try
            {
                fileStream = new FileStream(workflowPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                try
                {
                    program = ActivityXamlServices.Load(fileStream, xamlSettings);

                    ValidationResults results = null;

                    //If this is a Dynamic activity - a XamlException might occur
                    try
                    {
                        results = ActivityValidationServices.Validate(program);

                        foreach (ValidationError warning in results.Warnings)
                        {
                            logger.Warn("Workflow program {0} has warning : {1}, Activity : {2}", workflowXaml, warning.Message, warning.Source.DisplayName);
                        }

                        foreach (ValidationError error in results.Errors)
                        {
                            logger.Error("Workflow program {0} has error : {1}, Activity : {2}", workflowXaml, error.Message, error.Source.DisplayName);
                        }

                        if (results.Errors.Count > 0)
                        {
                            logger.Error("Could not run Workflow: {0}", workflowXaml);

                            throw new CouldNotRunWorkflowProgramException(resolver.Resolve<IResourceManager>(),
                                workflowXaml, results.Errors.ToArray());
                        }
                    }
                    catch (XamlException xamlException)
                    {
                        logger.Error("Could not load workflow program with xaml {0} in directory {1}.\n\nException occurred : {2}.", workflowXaml, _directoryPath, xamlException.ToString());
                        throw new CouldNotLoadWorkflowProgramException(resolver.Resolve<IResourceManager>(),
                            workflowXaml, xamlException);
                    }
                }
                catch (XmlException xmlException)
                {
                    logger.Error("Could not load workflow program with xaml {0} in directory {1}.\n\nException occurred : {2}.", workflowXaml, _directoryPath, xmlException.ToString());
                    throw new CouldNotLoadWorkflowProgramException(resolver.Resolve<IResourceManager>(),
                        workflowXaml, xmlException);
                }
                catch (XamlException xamlException)
                {
                    logger.Error("Could not load workflow program with xaml {0} in directory {1}.\n\nException occurred : {2}.", workflowXaml, _directoryPath, xamlException.ToString());
                    throw new CouldNotLoadWorkflowProgramException(resolver.Resolve<IResourceManager>(),
                        workflowXaml, xamlException);
                }
                catch (ArgumentException argumentException)
                {
                    logger.Error("Could not load workflow program with xaml {0} in directory {1}.\n\nException occurred : {2}.", workflowXaml, _directoryPath, argumentException.ToString());
                    throw new CouldNotLoadWorkflowProgramException(resolver.Resolve<IResourceManager>(),
                        workflowXaml, argumentException);
                }
            }
            catch (FileNotFoundException fileNotFoundException)
            {
                logger.Error("Could not find workflow program with xaml {0} in directory {1}.\n\nException occurred : {2}.", workflowXaml, _directoryPath, fileNotFoundException.ToString());
                throw new CouldNotFindWorkflowProgramException(resolver.Resolve<IResourceManager>(),
                    workflowXaml);
            }
            catch (IOException ioException)
            {
                logger.Error("Could not read workflow program file {0} due to an IO Exception {1}.", workflowPath, ioException);
                throw new CouldNotReadWorkflowProgramException(resolver.Resolve<IResourceManager>(),
                    workflowXaml, ioException);
            }
            catch (Exception e)
            {
                logger.Error("Error occurred when trying find and load workflow program {0}.\n\nError : {1}.", workflowPath, e);
                throw;
            }
            finally
            {
                if (fileStream != null)
                {
                    fileStream.Dispose();
                }
            }

            #endregion

            #region Recognize Arguments

            inputs = TryRecognizeWorkflowArguments(program);

            #endregion

            return program;
        }

        #endregion

        #region Private Methods

        private IDictionary<string, DynamicActivityProperty> TryRecognizeWorkflowArguments(Activity program)
        {
            IDictionary<string, DynamicActivityProperty> inputs = new Dictionary<string, DynamicActivityProperty>();

            DynamicActivity dynamicActivity = program as DynamicActivity;
            if (dynamicActivity != null)
            {
                // In this case arguments accesses as Properties
                if (dynamicActivity.Properties != null && dynamicActivity.Properties.Count > 0)
                {
                    foreach (var property in dynamicActivity.Properties)
                    {
                        inputs.Add(property.Name, property);
                    }
                }
            }
            else
            {
                // Use reflection to retrieve activity arguments 
                var properties = program.GetType()
                    .GetProperties()
                    .Where(p => typeof(InArgument).IsAssignableFrom(p.PropertyType))
                    .ToList();

                if (properties != null && properties.Count > 0)
                {
                    foreach (var property in properties)
                    {
                        DynamicActivityProperty dynamicActivityProperty = new DynamicActivityProperty()
                        {
                            Name = property.Name,
                            Type = property.PropertyType
                            // TODO : Deep copy using reflection to set Attributes here
                        };
                    }
                }
            }

            return inputs;
        }

        #endregion
    }
}
